package svgedit.gui;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Locale;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.KeyStroke;

import svgedit.gui.actions.DeleteAction;
import svgedit.gui.actions.EditDocumentPropertiesAction;
import svgedit.gui.actions.ExportDocumentAsAction;
import svgedit.gui.actions.GroupAction;
import svgedit.gui.actions.InsertCircleElementAction;
import svgedit.gui.actions.InsertLineElementAction;
import svgedit.gui.actions.InsertRectElementAction;
import svgedit.gui.actions.LanguageAction;
import svgedit.gui.actions.NewDocumentAction;
import svgedit.gui.actions.OpenDocumentAction;
import svgedit.gui.actions.QuitAction;
import svgedit.gui.actions.RedoAction;
import svgedit.gui.actions.SaveDocumentAction;
import svgedit.gui.actions.SaveDocumentAsAction;
import svgedit.gui.actions.SelectAllAction;
import svgedit.gui.actions.UndoAction;
import svgedit.gui.actions.UngroupAction;
import svgedit.gui.commands.CommandStack;
import svgedit.svg.SVGDocument;
import svgedit.svg.SVGElement;
import svgedit.svg.SVGGroup;
import svgedit.svg.SVGLength;
import svgedit.svg.SVGPaint;
import svgedit.svg.SVGStylable;
import svgedit.xml.XMLReader;
import svgedit.xml.XMLWriter;

import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileNameExtensionFilter;

/** Displays a window containing a SVG view, toolbar and menu. */
@SuppressWarnings("serial")
public class Frame extends JFrame
{

	// Singleton reference
	private static Frame instance = null;

	/** User preferences */
	private Preferences preferences;

	/** The document displayed by the frame */
	private SVGDocument document;

	/** An SVG view of the current document */
	private View view;

	// Actions available from the menu and toolbar */
	private Action newAction;
	private Action openAction;
	private Action saveAction;
	private Action saveAsAction;
	private ExportDocumentAsAction exportAsAction;
	private Action documentPropertiesAction;
	private Action quitAction;

	// Actions for modifying the current selection */
	private Action selectAllAction;
	private Action groupAction;
	private Action ungroupAction;
	private Action deleteAction;

	// Actions for undo/redo
	private Action undoAction;
	private Action redoAction;

	// Actions for adding new elements to the document */
	private Action insertRectAction;
	private Action insertCircleAction;
	private Action insertLineAction;
	private JToggleButton[] toolBarButtons;

	private PaintDropDown fillColorPicker;
	private PaintDropDown strokeColorPicker;
	private JTextField strokeWidthTextField;

	// Containers that are required for language changes
	private Locale locale;
	private JToolBar toolBar;
	private JMenuBar menuBar;
	private JMenu subLangMenu;

	
	
	
	private class FrameWindowListener extends WindowAdapter
	{

		/** Invokes quit action when user attempts to close window */
		@Override
		public void windowClosing(WindowEvent we)
		{
			quitAction.actionPerformed(null);
		}
	}

	/** Creates a frame with a new, empty document. */
	private Frame()
	{
		preferences = new Preferences();

		// Create all actions presented by the menu bar and toolbar

		newAction = new NewDocumentAction(this);
		openAction = new OpenDocumentAction(this);
		saveAction = new SaveDocumentAction(this);
		saveAsAction = new SaveDocumentAsAction(this);
		exportAsAction = new ExportDocumentAsAction(this);
		documentPropertiesAction = new EditDocumentPropertiesAction(this);
		quitAction = new QuitAction(this);

		selectAllAction = new SelectAllAction(this);
		groupAction = new GroupAction(this);
		ungroupAction = new UngroupAction(this);
		deleteAction = new DeleteAction(this);

		undoAction = new UndoAction(this);
		redoAction = new RedoAction(this);
		undoAction.setEnabled(false);
		redoAction.setEnabled(false);

		insertRectAction = new InsertRectElementAction(this);
		insertCircleAction = new InsertCircleElementAction(this);
		insertLineAction = new InsertLineElementAction(this);

		// Associate keyboard shortcuts with actions

		newAction.putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke
				.getKeyStroke(KeyEvent.VK_N, Toolkit.getDefaultToolkit()
						.getMenuShortcutKeyMask()));
		openAction.putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke
				.getKeyStroke(KeyEvent.VK_O, Toolkit.getDefaultToolkit()
						.getMenuShortcutKeyMask()));
		saveAction.putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke
				.getKeyStroke(KeyEvent.VK_S, Toolkit.getDefaultToolkit()
						.getMenuShortcutKeyMask()));
		saveAsAction
				.putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke
						.getKeyStroke(KeyEvent.VK_S, KeyEvent.SHIFT_MASK
								| Toolkit.getDefaultToolkit()
										.getMenuShortcutKeyMask()));

		selectAllAction.putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke
				.getKeyStroke(KeyEvent.VK_A, Toolkit.getDefaultToolkit()
						.getMenuShortcutKeyMask()));
		groupAction.putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke
				.getKeyStroke(KeyEvent.VK_G, Toolkit.getDefaultToolkit()
						.getMenuShortcutKeyMask()));
		ungroupAction
				.putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke
						.getKeyStroke(KeyEvent.VK_G, KeyEvent.SHIFT_MASK
								| Toolkit.getDefaultToolkit()
										.getMenuShortcutKeyMask()));

		undoAction.putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke
				.getKeyStroke(KeyEvent.VK_Z, Toolkit.getDefaultToolkit()
						.getMenuShortcutKeyMask()));
		redoAction.putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke
				.getKeyStroke(KeyEvent.VK_Y, Toolkit.getDefaultToolkit()
						.getMenuShortcutKeyMask()));

		// Hook up handler to window close event to display save confirmation
		// dialog.

		setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
		addWindowListener(new FrameWindowListener());

		// Create menus

		menuBar = new JMenuBar();
		add(menuBar, BorderLayout.NORTH);

		JMenu fileMenu = new JMenu("File");
		subLangMenu = new JMenu("Language...");
		fileMenu.add(new JMenuItem(newAction));
		fileMenu.add(new JMenuItem(openAction));
		fileMenu.addSeparator();
		fileMenu.add(new JMenuItem(saveAction));
		fileMenu.add(new JMenuItem(saveAsAction));
		fileMenu.add(new JMenuItem(exportAsAction));
		fileMenu.addSeparator();
		fileMenu.add(new JMenuItem(documentPropertiesAction));
		subLangMenu.add(new JMenuItem(new LanguageAction(this, "English")));
		subLangMenu.add(new JMenuItem(new LanguageAction(this, "German")));
		subLangMenu.add(new JMenuItem(new LanguageAction(this, "Japanese")));
		fileMenu.add(subLangMenu);
		fileMenu.addSeparator();
		fileMenu.add(new JMenuItem(quitAction));
		menuBar.add(fileMenu);

		JMenu editMenu = new JMenu("Edit");
		editMenu.add(new JMenuItem(undoAction));
		editMenu.add(new JMenuItem(redoAction));
		editMenu.addSeparator();
		editMenu.add(new JMenuItem(selectAllAction));
		editMenu.addSeparator();
		editMenu.add(new JMenuItem(groupAction));
		editMenu.add(new JMenuItem(ungroupAction));
		editMenu.addSeparator();
		editMenu.add(new JMenuItem(deleteAction));
		menuBar.add(editMenu);

		JMenu insertMenu = new JMenu("Insert");
		insertMenu.add(new JMenuItem(insertRectAction));
		insertMenu.add(new JMenuItem(insertCircleAction));
		insertMenu.add(new JMenuItem(insertLineAction));
		menuBar.add(insertMenu);

		// Create toolbar

		fillColorPicker = new PaintDropDown(PaintDropDown.PAINT_ATTRIBUTE_FILL);
		fillColorPicker.addItemListener(new ItemListener()
		{
			public void itemStateChanged(ItemEvent ie)
			{
				SVGPaint paint = fillColorPicker.getPaint();
				view.setSelectedFill(paint);
				view.getDefaultStyle().getFill().setValueFromPaint(paint);
			}
		});

		strokeColorPicker = new PaintDropDown(
				PaintDropDown.PAINT_ATTRIBUTE_STROKE);
		strokeColorPicker.addItemListener(new ItemListener()
		{
			public void itemStateChanged(ItemEvent ie)
			{
				SVGPaint paint = strokeColorPicker.getPaint();
				view.setSelectedStroke(paint);
				view.getDefaultStyle().getStroke().setValueFromPaint(paint);
			}
		});

		strokeWidthTextField = new JTextField();
		strokeWidthTextField.getDocument().addDocumentListener(
				new DocumentChangedAdapter()
				{

					@Override
					public void documentChanged()
					{
						try
						{
							SVGLength length = new SVGLength();
							length.setValueFromString(strokeWidthTextField
									.getText());
							view.setSelectedStrokeWidth(length);
							view.getDefaultStyle().getStrokeWidth()
									.setValueFromLength(length);
						}
						catch (NumberFormatException e)
						{
							// Ignore parse error; the user may still be typing
						}
					}

				});
		strokeWidthTextField.addFocusListener(new FocusAdapter()
		{

			@Override
			public void focusLost(FocusEvent fe)
			{
				// If invalid text was left in the text field, restore the
				// text to the current selection
				view.setSelectedElements(view.getSelectedElements());
			}

		});

		toolBarButtons = new JToggleButton[] {
				new JToggleButton(insertRectAction),
				new JToggleButton(insertCircleAction),
				new JToggleButton(insertLineAction) };

		toolBar = new JToolBar();
		toolBar.add(newAction);
		toolBar.add(openAction);
		toolBar.add(saveAction);
		toolBar.add(new JToolBar.Separator());
		for (JToggleButton button : toolBarButtons)
			toolBar.add(button);
		toolBar.add(new JToolBar.Separator());
		toolBar.add(new JLabel("Fill:"));
		toolBar.add(fillColorPicker);
		toolBar.add(new JLabel("Stroke:"));
		toolBar.add(strokeColorPicker);
		toolBar.add(new JLabel("Stroke Width:"));
		toolBar.add(strokeWidthTextField);

		JPanel viewContainer = new JPanel();
		viewContainer.setLayout(new BorderLayout());
		add(viewContainer);

		viewContainer.add(toolBar, BorderLayout.NORTH);

		// Create view

		view = new View();
		view.addViewListener(new ViewListener()
		{
			public void viewSelectionChanged(View view)
			{
				selectionChanged();
			}

			public void viewManipulatorChanged(View view)
			{
				setManipulatorAction(null);
			}
		});
		viewContainer.add(view);

		// Map delete and backspace keys to delete action
		view.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0),
				"delete");
		view.getInputMap().put(
				KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), "delete");
		view.getActionMap().put("delete", deleteAction);

		// Always start with blank canvas
		newFile();

		view.setSelectionManipulator();

		// Set an initial view size in case the document size is based on the
		// view
		view.setSize(new Dimension(480, 360));

		// Size the view to the document
		view.setPreferredSize(new Dimension((int) document.getWidth()
				.getValue() + 20, (int) document.getHeight().getValue() + 20));
		pack();

		setLocationRelativeTo(null);

	}

	/**
	 * Gets the preferences object to use for getting and setting user defaults.
	 * 
	 * @return the preferences object associated with this frame
	 */
	public Preferences getPreferences()
	{
		return preferences;
	}

	/**
	 * Gets the document currently displayed in the frame.
	 * 
	 * @return an SVG document
	 */
	public SVGDocument getDocument()
	{
		return document;
	}

	/**
	 * Sets the document currently displayed in the frame.
	 * 
	 * @param document
	 *            the document to display
	 * @param file
	 *            the file the document was loaded from, or null if it's a new
	 *            file
	 */
	private void setDocument(SVGDocument document, File file)
	{
		this.document = document;
		view.setDocument(document);
		setFile(file);
	}

	/** Gets the view presenting the document for this frame. */
	public View getView()
	{
		return view;
	}

	/**
	 * Sets the action responsible for the view's current manipulator. This
	 * causes that action's toolbar button to appear selected, and for all other
	 * toolbar buttons to unselect.
	 * 
	 * @param action
	 *            the action that set the view's manipulator
	 */
	public final void setManipulatorAction(Action action)
	{
		for (JToggleButton button : toolBarButtons)
		{
			button.setSelected(button.getAction() == action);
		}
	}

	/**
	 * Set the filename of the current document. Sets the frame's title and
	 * enables the appropriate save menu items.
	 * 
	 * @param file
	 *            the filename of the current document, or null if the document
	 *            is new and has no filename
	 */
	private void setFile(File file)
	{
		document.setFile(file);

		if (file != null)
			setTitle(file.getName() + " - SVGEdit");
		else
			setTitle("SVGEdit");
	}

	/** Sets the document to a new document with no associated file. */
	public final void newFile()
	{
		SVGDocument doc = new SVGDocument(view);
		setDocument(doc, null);
	}

	/**
	 * Loads a file and sets it as the current document. Displays an error
	 * message and leaves the current document unchanged if the document cannot
	 * be opened.
	 * 
	 * @param file
	 *            the file to load
	 */
	public void openFile(File file)
	{
		try
		{
			SVGDocument doc = XMLReader.read(file, view);
			setDocument(doc, file);
		}
		catch (IOException e)
		{
			JOptionPane.showMessageDialog(getRootPane(), e.getMessage(),
					"Unable to open file", JOptionPane.ERROR_MESSAGE);
		}
	}

	/**
	 * Writes the current document to the given file.
	 * 
	 * @param file
	 *            the file to save to
	 */
	public void saveFile(File file)
	{
		assert document != null;
		assert file != null;

		try
		{
			XMLWriter.write(document, file);
			setFile(file);
			document.setModified(false);
		}
		catch (IOException e)
		{
			JOptionPane.showMessageDialog(getRootPane(), e.getMessage(),
					"Unable to save file", JOptionPane.ERROR_MESSAGE);
		}
	}

	/**
	 * If the document has unsaved modifications, prompt the user to save
	 * changes.
	 * 
	 * @return false if the user pressed 'Cancel' at the dialog, otherwise true
	 */
	public boolean confirmSaveChanges()
	{
		if (document.getModified())
		{
			int result = JOptionPane.showConfirmDialog(null,
					"Save changes to current document?", "Save Changes",
					JOptionPane.YES_NO_CANCEL_OPTION,
					JOptionPane.WARNING_MESSAGE);

			if (result == JOptionPane.CANCEL_OPTION)
				return false;

			if (result == JOptionPane.OK_OPTION)
			{
				if (document.getFile() != null)
					saveAction.actionPerformed(null);
				else
					saveAsAction.actionPerformed(null);
			}
		}

		CommandStack.getInstance().clear();

		return true;
	}

	/**
	 * Handler called when the view's selection changes. Updates the toolbar
	 * elements to display the properties of the current selection, and enables
	 * relevant actions for the current selection.
	 */
	private void selectionChanged()
	{
		SVGElement[] selectedElements = view.getSelectedElements();

		SVGStylable style;
		if (selectedElements.length != 0)
			style = view.getSelectionStyle();
		else
			style = view.getDefaultStyle();

		view.setUpdatingTooblar(true);

		fillColorPicker.setPaint(style.getFill());
		strokeColorPicker.setPaint(style.getStroke());
		if (style.getStrokeWidth() != null)
			strokeWidthTextField
					.setText(style.getStrokeWidth().valueAsString());
		else
			strokeWidthTextField.setText("");

		view.setUpdatingTooblar(false);

		// Enable delete action if elements are selected
		deleteAction.setEnabled(selectedElements.length > 0);

		// Enable group action if more than one element is selected
		groupAction.setEnabled(selectedElements.length > 1);

		// Enable ungroup action if a group is selected
		ungroupAction.setEnabled(false);
		for (SVGElement elem : selectedElements)
		{
			if (elem instanceof SVGGroup)
				ungroupAction.setEnabled(true);
		}
	}

	/**
	 * Updates the undo redo menu items to be enabled/disabled appropriately
	 */
	public void updateCommandMenus()
	{
		// Update menu items
		undoAction.setEnabled((CommandStack.getInstance().isUndoable()));
		redoAction.setEnabled((CommandStack.getInstance().isRedoable()));

	}
	
	/**
	 * Get the singleton instance
	 * 
	 * @return The singleton instance of the class
	 */
	public static Frame getInstance()
	{
		if (instance == null)
			instance = new Frame();

		return instance;
	}

	/**
	 * Gets a value from the current locale's Resource Bundle, taking
	 * the values corresponding key.
	 * @param key
	 * @return Resources Value
	 */
	public String getLanguageValue(String key)
    {
    	ResourceBundle labels = PropertyResourceBundle.getBundle(
				"svgedit.gui.actions.LabelsBundle", Frame.getInstance().getLocale());
		
		if(Frame.getInstance().getLocale().toString().compareTo(Locale.JAPAN.toString())==0)
		{
			try
			{
				//Cause java can be gay, a UTF-8 conversion is necessary
				//to display Japanese characters.
				String original = (String) labels.getString(key);
				return new String(original.getBytes("ISO-8859-1"), "UTF-8");
			
			} 
			catch (UnsupportedEncodingException e)
			{
				e.printStackTrace();
				return labels.getString(key);
			}
		}
		else
			return labels.getString(key);
    }
	
	/**
	 * Gets the JToolBar used on this frame
	 * 
	 * @return JToolBar
	 */
	public JToolBar getJToolBar()
	{
		return toolBar;
	}

	/**
	 * Gets the JMenuBar used on this frame
	 * 
	 * @return JMenuBar
	 */
	public JMenuBar getJMenuBar()
	{
		return menuBar;
	}

	/**
	 * Gets an array of JToggleButtons used on the toolbar in this frame
	 * 
	 * @return JToggleButton[]
	 */
	public JToggleButton[] getToolbarButtons()
	{
		return toolBarButtons;
	}

	/**
	 * Gets the JMenu submenu for languages
	 * 
	 * @return JMenu
	 */
	public JMenu getLangMenu()
	{
		return subLangMenu;
	}

	/**
	 * Gets the current Locale instance in the frame
	 * 
	 * @return Locale
	 */
	public Locale getLocale()
	{
		return locale;
	}

	/**
	 * Sets the current Locale instance in the frame takes a Locale
	 * 
	 * @param locale
	 */
	public void setLocale(Locale locale)
	{
		this.locale = locale;
	}

	/**
	 * FileFilters used in the program. Why create three classes when one 
	 * already exists for our purpose?
	 * @author Ross
	 *
	 */
	public static class FileFilters
	{
	    /** File filter for SVG Files
		 * @see javax.swing.filechooser.FileNameExtensionFilter */
		public static final FileFilter SVG_FILE_FILTER =  
		    new FileNameExtensionFilter("Scalable Vector Graphic (SVG) Files", "svg");
		
		/** File filter for Compressed SVG Files (SVGZ)
		 * @see javax.swing.filechooser.FileNameExtensionFilter */
		public static final FileFilter SVGZ_FILE_FILTER =  
		    new FileNameExtensionFilter("Compressed Scalable Vector Graphic (SVGZ) Files", "svgz");
		
		/** File filter for PNG Files
		 * @see javax.swing.filechooser.FileNameExtensionFilter */	
		public static final FileFilter PNG_FILE_FILTER =  
		    new FileNameExtensionFilter("Portable Network Graphic (PNG) Files", "png");
		
		/** File filter for SVG Files
		 * @see javax.swing.filechooser.FileNameExtensionFilter */
		public static final FileFilter PDF_FILE_FILTER =  
		    new FileNameExtensionFilter("Portable Network Document (PDF) Files", "pdf");
		
	}
	
}
